from datetime import date
from decimal import ROUND_HALF_UP, Decimal
import akshare as ak
from config.get_redis import RedisUtil
from config.database import AsyncSessionLocal
from module_stock.dao.basic_dao import BasicDao
from utils.ak_util import filter_ak_stock
from utils.log_util import logger
from module_stock.service.kline_boll_service import KlineBollService
from sqlalchemy.ext.asyncio import AsyncSession

from utils.stock_util import CommonUtil

is_init_boll_cache = 0

async def gen_kline_boll_job(kline_period:str): 
    """
    生成股票的boll数据, 首先需要依赖基础数据的同步
    1.查询股票基础信息表
    2 
    2.1如果没有数据，说明基础数据表未同步，提示同步基础数据表未同步的错误
    2.2如果有数据，则查询bollK线表所有的股票去重股票代码，并与基础信息表数据比对
    3.2.1 基础信息表有的，而K线boll表没有，说明是新增，初始化K线boll表
    3.2.2 基础信息表没有，而K线boll表有的，从K线boll表进行删除
    3.2.3 都有的数据，进行迭代获取股票K线boll表最后一个交易日的数据，获得日期，根据该日期查询该日期之后的K线表数据，
    比对最后一个的价格，如果不一致，说明股票进行了复权，将该股票K线boll在数据库删除，并重新初始化
    如果一致，根据该日期之后的K线数据进行计算增量boll计算和存储
    :param 
    :return: 
    """
    try:
        async with AsyncSessionLocal() as session:
            symbols_in_basic = await BasicDao.get_all_symbols(session)
            
            if symbols_in_basic and len(symbols_in_basic) > 0 :
                await KlineBollService.clear_bolls(session, kline_period) 
                KlineBollService.clear_boll_cache(kline_period)           
                for symbol_basic in symbols_in_basic:  
                    for x_type in KlineBollService.x_types:
                        await KlineBollService.init_boll(session, symbol_basic,
                            x_type['data_type'], x_type['lower_type'], x_type['upper_type'], kline_period)
                await KlineBollService.init_boll_cache(session, kline_period)
            else:
                #没有数据
                raise Exception("股票基础信息表未同步")
            await session.commit()
    except Exception as e:
        await session.rollback()
        raise e
    
async def gen_kline_realtime_job(): 
    """
    生成股票的实时boll数据
    1.查询股票基础信息表
    2 
    2.1如果没有数据，说明基础数据表未同步，提示同步基础数据表未同步的错误
    2.2如果有数据，则从akshare查询实时数据，计算实时boll
    :param 
    :return: 
    """
    try:
        async with AsyncSessionLocal() as session:
            if not is_init_boll_cache:
                logger.info("boll 缓存未初始化")
                return

            #实时行情
            stock_df = ak.stock_zh_a_spot_em()
            stock_df = filter_ak_stock(stock_df, '代码', '名称')
            transform_colums = ['今开', '最新价', '最高', '最低']

            stock_df[transform_colums] = stock_df[transform_colums].map(
                lambda x: Decimal(str(x)).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
            )
            
            # stock_zh_a_spot_em_df = pd.DataFrame([{
            #     "代码" : "000001",
            #     "最新价" : Decimal(12.99).quantize(Decimal('0.00')),
            #     "最高" : Decimal(13.17).quantize(Decimal('0.00')),
            #     "最低" : Decimal(12.86).quantize(Decimal('0.00')),
            #     "今开": Decimal(12.87).quantize(Decimal('0.00'))
            # }]
            # )
            for row in stock_df.itertuples():
                try :               
                    await gen_boll_realtime(session, row)
                except Exception as e:
                    logger.error(e)
    except Exception as e:
        await session.rollback()
        raise e
    
async def gen_boll_realtime(session: AsyncSession, row:tuple):
    if not is_init_boll_cache:
        logger.info("boll 缓存未初始化")
        return
    
    symbol = row.代码
    realtime_gen_data = {
        'trade_date':date.today(),
        'open': row.今开, 
        'close': row.最新价, 
        'low': row.最低, 
        'high':row.最高
    }
    #日线boll侦测
    kline_period = 'daily'
    for x_type in KlineBollService.x_types:
        await KlineBollService.detect_realtime_divergence(session, symbol, realtime_gen_data, 
                                RedisUtil.get_redis(), kline_period, 
            x_type['data_type'], x_type['lower_type'], x_type['upper_type'])

    #周线boll侦测
    kline_period = 'weekly'
    weekly_start_date =  CommonUtil.get_period_startdate(kline_period)
    bolls = KlineBollService.get_bolls_from_cache(symbol, weekly_start_date, kline_period)
    realtime_dect_data = realtime_gen_data
    if bolls : 
        open = bolls[0]['open']
        lows = [boll['low']  for boll in bolls]
        low = min(min(lows),row.最低)
        highs =  [boll['high']  for boll in bolls]
        high = max(max(highs),row.最高)
        close = row.最新价
        realtime_dect_data = {
            'trade_date':date.today(),
            'open': open, 
            'close': close, 
            'low': low, 
            'high': high
        }
    for x_type in KlineBollService.x_types:
        await KlineBollService.detect_realtime_divergence(session, symbol, realtime_dect_data, 
                                RedisUtil.get_redis(), kline_period, 
            x_type['data_type'], x_type['lower_type'], x_type['upper_type'])


    #月线boll侦测
    kline_period = 'monthly'
    monthly_start_date =  CommonUtil.get_period_startdate(kline_period)
    bolls = KlineBollService.get_bolls_from_cache(symbol, monthly_start_date, kline_period)
    realtime_dect_data = realtime_gen_data
    if bolls : 
        open = bolls[0]['open']
        lows = [boll['low']  for boll in bolls]
        low = min(min(lows),row.最低)
        highs =  [boll['high']  for boll in bolls]
        high = max(max(highs),row.最高)
        close = row.最新价
        realtime_dect_data = {
            'trade_date':date.today(),
            'open': open, 
            'close': close, 
            'low': low, 
            'high': high
        }
        for x_type in KlineBollService.x_types:
            await KlineBollService.detect_realtime_divergence(session, symbol, realtime_dect_data, 
                                    RedisUtil.get_redis(), kline_period, 
                x_type['data_type'], x_type['lower_type'], x_type['upper_type'])
            
async def init_boll_cache():
    """
    初始化boll数据缓存

    :return:
    """
    async with AsyncSessionLocal() as session:
        await KlineBollService.init_boll_cache(session)
        global is_init_boll_cache
        is_init_boll_cache = 1